package org.longteng.sensitive.mybatis.intercepts;

import cn.hutool.core.util.HexUtil;
import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.symmetric.AES;
import org.longteng.sensitive.utils.SensitiveUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 数据库字段加解密handler,JdbcType.VARCHAR：表示该处理器处理的数据类型
 * <br>数据表中，参与查询的字段用此加密
 * MappedJdbcTypes(JdbcType.VARCHAR) 会全部model字段拦截生效
 * MappedTypes(AuthUser.class)
 * <br>启动配置文件加：这个会导致全局注册
 * mybatis-plus.type-handlers-package=com.huawei.elinkbus.intercepts
 * <br>通过参数或结果Results、resultMap 指定typeHandler = com.huawei.elinkbus.intercepts.EncryptHandler生效
 */
@MappedJdbcTypes(JdbcType.VARCHAR)
@Slf4j
public class EncryptHandler extends BaseTypeHandler<String> {
    /**
     * 线上运行后不修改，会影响已加密数据解密
     * Key length not 128/192/256 bits.
     * 128 位 对应的是 16 个字节
     * 192 位 对应的是 24 个字节
     * 256 位 对应的是 32 个字节
     */
    private static AES aesCbc;

    /**
     * 构建
     */
    public EncryptHandler() {
        super();
        if (aesCbc == null) {
            byte[] keys = SensitiveUtil.getBytes(HexUtil.encodeHexStr(SensitiveUtil.KEY_STR), 32);
            byte[] iv = SensitiveUtil.getBytes(SensitiveUtil.IV_KEY, 16);

            aesCbc = new AES(Mode.CBC, Padding.PKCS5Padding, keys, iv);
            if (log.isDebugEnabled()) {
                log.debug("创建字段加解密handler成功，CBC。");
            }
        }
    }

    /**
     * 设置参数,加密
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        if (StringUtils.isEmpty(parameter)) {
            ps.setString(i, null);
            return;
        }
        if(log.isDebugEnabled()) {
            log.debug("设置参数,加密，parameter:{}", parameter);
        }
        String encrypt = this.encrypt(parameter);
        ps.setString(i, encrypt);
    }

    /**
     * 获取值，解密
     */
    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return decrypt(rs.getString(columnName));
    }

    /**
     * 获取值，解密
     */
    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return decrypt(rs.getString(columnIndex));
    }

    /**
     * 获取值，解密
     */
    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return decrypt(cs.getString(columnIndex));
    }

    /**
     * 加密
     * @param value
     * @return
     */
    public String encrypt(String value) {
        if (null == value) {
            return null;
        }
        try {
            String result = aesCbc.encryptHex(value);
            return result;
        } catch (CryptoException e) {
            if(log.isDebugEnabled()) {
                log.debug("字段拦截加密的值：{}", value);
            }
            log.error("字段拦截加密错误.", e);
        }
        return value;

    }

    /**
     * 解密
     * @param value
     * @return
     */
    public String decrypt(String value) {
        if (null == value) {
            return null;
        }
        try {
            String result = aesCbc.decryptStr(value);
            return result;
        } catch (CryptoException e) {
            if(log.isDebugEnabled()) {
                log.debug("字段拦截解密的值：{}", value);
            }
            log.error("字段拦截解密错误.", e);
        }
        return value;
    }

}
